//                 _              
//                | |             
//   __ _ _ __ ___| |__ __  _____ 
//  / _` | '__/ __| '_ \\ \/ / __|
// | (_| | | | (__| | | |>  <\__ \
//  \__,_|_|  \___|_| |_/_/\_\___/
//                                
// Copyright 2010 Scott Dixon http://www.archxs.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package com.archxs.commons;

import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import org.junit.Assert;
import org.junit.Test;

import com.someoneelse.TestVariantExtension;


/**
 * Core unit tests of {@link Variant} and its primary functions.
 */
public class VariantTest {

    /**
     * Test each output type against a null input value.
     */
    @Test
    public void testGetFromNull() {
        final Variant test = new Variant((String)null);
        Assert.assertEquals("null", test.stringValue());
        Assert.assertNull(test.getNormalizedString());
    }
    
    /**
     * Test retrieval of a string.
     */
    @Test
    public void testGetString() {
        final Variant test = new Variant("foobar");
        Assert.assertEquals("foobar", test.stringValue());
    }
    
    @Test
    public void testNullName() {
        Assert.assertEquals(String.format("%s",(String)null), Variant.NULL_NAME);
    }
    
    @Test
    public void testGetNormalizedString() {
        final Variant test = Variant.valueOf("foo");
        Assert.assertEquals("foo", test.getNormalizedString());
        Assert.assertEquals("foo", test.getNormalizedString(test.getLocale()));
    }
    
    @Test
    public void testNormalization_lowerCase() {
        Assert.assertEquals("fooπ", Variant.valueOf("Fooπ").getNormalizedString());
        Assert.assertEquals("fooπ", Variant.valueOf("fOoπ").getNormalizedString());
        Assert.assertEquals("foo\u03C2", Variant.valueOf("FOO\u03A3").getNormalizedString());
        Assert.assertEquals("foo\u0131", Variant.valueOf("FOO\u0049").getNormalizedString(new Locale("tr")));
    }
    
    @Test
    public void testNormalization_trim() {
        Assert.assertEquals("foo", Variant.valueOf(" foo").getNormalizedString());
        Assert.assertEquals("foo", Variant.valueOf("\tfoo ").getNormalizedString());
        Assert.assertEquals("foo", Variant.valueOf("\nfoo ").getNormalizedString());
    }
    
    @Test
    public void testNormalization_trimWNBSP() {
        // u+00A0 Non-breaking space
        Assert.assertEquals("foo", Variant.valueOf("\u00A0foo ").getNormalizedString());
    }
    
    @Test
    public void testNormalization_trimWIdeoGraphicSpace() {
        // u+3000 Ideographic space
        Assert.assertEquals("foo", Variant.valueOf("\u3000foo ").getNormalizedString());
    }
    
    @Test
    public void testNormalization_trimCoverAllUnicodeSpace() {
        for(final int unicodeCodePoint : Variant.UNICODE_SPACES) {
            final StringBuilder stringWithUnicodeWhitespace = new StringBuilder(" ");
            stringWithUnicodeWhitespace.appendCodePoint(unicodeCodePoint);
            stringWithUnicodeWhitespace.append("fOo ");
            Assert.assertEquals("foo", Variant.valueOf(stringWithUnicodeWhitespace.toString()).getNormalizedString());
        }
    }
    
    @Test
    public void testLoadFromProperties() {
        final ResourceBundle testBundle = ResourceBundle.getBundle(String.format("%s.varianttest",getClass().getPackage().getName()));
        try {
            Variant.getPropertyAsVariant(testBundle, "notThere");
            Assert.fail("MissingResourceException was not thrown");
        } catch (final MissingResourceException e) {
            // yep.
        }
        final Variant defaultValue = Variant.getPropertyAsVariant(testBundle, "notThere", "default");
        Assert.assertEquals("default", defaultValue.stringValue());
        
        try {
            Variant.getPropertyAsVariant(null, "foo");
            Assert.fail("IAE exception excpected but was not thrown.");
        } catch (final IllegalArgumentException e) {
            //OKAY
        }
        try {
            Variant.getPropertyAsVariant(testBundle, null);
            Assert.fail("IAE exception excpected but was not thrown.");
        } catch (final IllegalArgumentException e) {
            //OKAY
        }
        try {
            Variant.getPropertyAsVariant(null, "foo", "hi mum");
            Assert.fail("IAE exception excpected but was not thrown.");
        } catch (final IllegalArgumentException e) {
            //OKAY
        }
        try {
            Variant.getPropertyAsVariant(testBundle, null, "hi mum");
            Assert.fail("IAE exception excpected but was not thrown.");
        } catch (final IllegalArgumentException e) {
            //OKAY
        }
        final Variant stringFromProps = Variant.getPropertyAsVariant(testBundle, "basicString");
        Assert.assertEquals("foobar",stringFromProps.stringValue());
    }
    
    @Test
    public void testToString() {
        final String testString = "foobar";
        Assert.assertEquals(testString, (new Variant(testString)).toString());
    }
    
    @Test
    public void testHashCode() {
        final String testString = "foobar";
        Assert.assertEquals(testString.hashCode(), (new Variant(testString)).hashCode());
    }
    
    @Test
    public void testEquals() {
        final String testValue = "foobar";
        final Variant variant0 = new Variant(testValue);
        final Variant variant1 = new Variant(testValue);
        final Variant variant2 = new Variant("notfoobar");
        Assert.assertTrue(variant0.equals(variant1));
        Assert.assertTrue(variant1.equals(variant0));
        Assert.assertFalse(variant0.equals(testValue));
        Assert.assertFalse(variant0.equals(variant2));
        Assert.assertFalse(variant0.equals(null));
    }

    @Test
    public void testNoNPEForNoClassDynamicCast() {
        Assert.assertNull(Variant.<Object>dynamic_cast(null,Variant.valueOf("foo")));
        Assert.assertNull(Variant.<Integer>dynamic_cast(Integer.class,(Variant)null));
    }
    
    @Test
    public void testDynamicCast_nulls() {
        Assert.assertNull(Variant.<String>dynamic_cast(String.class,null));
        Assert.assertEquals(Variant.valueOf((String)null), Variant.<Variant>dynamic_cast(Variant.class, null));
        Assert.assertEquals(Variant.valueOf((String)null), Variant.<Variant[]>dynamic_cast(Variant[].class,null)[0]);
        Assert.assertEquals(Variant.valueOf((String)null), Variant.<Variant>dynamic_cast(TestVariantExtension.class,null));
        Assert.assertEquals(Variant.valueOf((String)null), Variant.<TestVariantExtension[]>dynamic_cast(TestVariantExtension[].class,null)[0]);
    }
    
    @Test
    public void testDynamicCast_booleans() {
        Assert.assertEquals(true, Variant.<Boolean>dynamic_cast(Boolean.class, Variant.valueOf(Boolean.TRUE.toString())).booleanValue());
        Assert.assertEquals(false, Variant.<Boolean>dynamic_cast(boolean.class, Variant.valueOf(Boolean.FALSE.toString())).booleanValue());
    }
    
    @Test
    public void testDynamicCast_numbers() {
        final Variant numberVariant = Variant.valueOf("1.9");
        Assert.assertEquals(numberVariant, Variant.<Variant>dynamic_cast(Variant.class, numberVariant));
        Assert.assertEquals("1.9", Variant.<String>dynamic_cast(String.class, numberVariant));
        Assert.assertEquals(1.9d, Variant.<Number>dynamic_cast(Number.class, numberVariant).doubleValue(), 0.02);
        Assert.assertEquals(1.9d, Variant.dynamic_cast(double.class, numberVariant).doubleValue(), 0.02);
        Assert.assertEquals(1.9d, Variant.<Double>dynamic_cast(Double.class, numberVariant).doubleValue(), 0.02);
        Assert.assertEquals(2, Variant.<Integer>dynamic_cast(Integer.class, numberVariant).intValue());
        Assert.assertEquals(2, Variant.dynamic_cast(int.class, numberVariant).intValue());
        Assert.assertEquals(1.9f, Variant.<Float>dynamic_cast(Float.class, numberVariant).floatValue(), 0.02);
        Assert.assertEquals(1.9f, Variant.dynamic_cast(float.class, numberVariant).floatValue(), 0.02);
        Assert.assertEquals(2l, Variant.<Long>dynamic_cast(Long.class, numberVariant).longValue());
        Assert.assertEquals(2l, Variant.dynamic_cast(long.class, numberVariant).longValue());
        Assert.assertEquals(2, Variant.<Short>dynamic_cast(Short.class, numberVariant).shortValue());
        Assert.assertEquals(2, Variant.dynamic_cast(short.class, numberVariant).shortValue());
    }
    
    @Test
    public void testDynamicCast_characters() {
        final Variant numberVariant = Variant.valueOf("1.9");
        Assert.assertEquals(2, Variant.<Character>dynamic_cast(Character.class, numberVariant).charValue());
        Assert.assertEquals(2, Variant.dynamic_cast(char.class, numberVariant).charValue());
        Assert.assertEquals('a', Variant.dynamic_cast(char.class, Variant.valueOf("abcdefg")).charValue());
        Assert.assertEquals('a', Variant.dynamic_cast(char.class, Variant.valueOf("a")).charValue());
        Assert.assertEquals('a', Variant.dynamic_cast(char.class, Variant.valueOf(new String[]{"a","b","c"})).charValue());
        Assert.assertEquals('a', Variant.dynamic_cast(char.class, Variant.valueOf(new String[]{"abc","bcd","cde"})).charValue());
        Assert.assertEquals('a', Variant.dynamic_cast(char.class, Variant.valueOf("[a,b,c]")).charValue());
    }
    
    @Test(expected=ClassCastException.class)
    public void testDynamicCast_arrays_primitives_void() {
        Variant.<Void[]>dynamic_cast(Void[].class, Variant.valueOf(new String[]{"1","2","3"}));
    }
    
    @Test(expected=ClassCastException.class)
    public void testDynamicCast_primitives_void_boxed() {
        Variant.<Void>dynamic_cast(Void.class, Variant.valueOf(new String[]{"1","2","3"}));
    }
    
    @Test(expected=ClassCastException.class)
    public void testDynamicCast_primitives_void() {
        Variant.dynamic_cast(void.class, Variant.valueOf(new String[]{"1","2","3"}));
    }
        
    @Test
    public void testDynamicCast_arrays_primitives_boolean() {
        final boolean[] primitiveArray = Variant.<boolean[]>dynamic_cast(boolean[].class, Variant.valueOf(new String[]{"true","false","true"}));
        Assert.assertNotNull(primitiveArray);
        Assert.assertEquals(3, primitiveArray.length);
        for(int i = 0; i < primitiveArray.length; ++i) {
            if (i%2==0) {
                Assert.assertTrue(primitiveArray[i]);
            } else {
                Assert.assertFalse(primitiveArray[i]);
            }
        }
    }
    
    @Test
    public void testDynamicCast_arrays_primitives_byte() {
        final byte[] primitiveArray = Variant.<byte[]>dynamic_cast(byte[].class, Variant.valueOf(new String[]{"0x00","0x01","0x02"}));
        Assert.assertNotNull(primitiveArray);
        Assert.assertEquals(3, primitiveArray.length);
        for(int i = 0; i < primitiveArray.length; ++i) {
            Assert.assertEquals((byte)i, primitiveArray[i]);
        }
    }
    
    @Test
    public void testDynamicCast_arrays_primitives_char() {
        final char[] primitiveArray = Variant.<char[]>dynamic_cast(char[].class, Variant.valueOf(new String[]{"abc","bcd","cde"}));
        Assert.assertNotNull(primitiveArray);
        Assert.assertEquals(3, primitiveArray.length);
        for(int i = 0; i < primitiveArray.length; ++i) {
            Assert.assertEquals('a' + (char)i, primitiveArray[i]);
        }
    }
    
    @Test
    public void testDynamicCast_arrays_primitives_short() {
        final short[] primitiveArray = Variant.<short[]>dynamic_cast(short[].class, Variant.valueOf(new String[]{"1","2","3"}));
        Assert.assertNotNull(primitiveArray);
        Assert.assertEquals(3, primitiveArray.length);
        for(int i = 0; i < primitiveArray.length; ++i) {
            Assert.assertEquals(i + 1, primitiveArray[i]);
        }
    }
    
    @Test
    public void testDynamicCast_arrays_primitives_int() {
        final int[] primitiveArray = Variant.<int[]>dynamic_cast(int[].class, Variant.valueOf(new String[]{"1","2","3"}));
        Assert.assertNotNull(primitiveArray);
        Assert.assertEquals(3, primitiveArray.length);
        for(int i = 0; i < primitiveArray.length; ++i) {
            Assert.assertEquals(i+1, primitiveArray[i]);
        }
    }
    
    @Test
    public void testDynamicCast_arrays_primitives_long() {
        final long[] primitiveArray = Variant.<long[]>dynamic_cast(long[].class, Variant.valueOf(new String[]{"1","2","3"}));
        Assert.assertNotNull(primitiveArray);
        Assert.assertEquals(3, primitiveArray.length);
        for(int i = 0; i < primitiveArray.length; ++i) {
            Assert.assertEquals(i + 1, primitiveArray[i]);
        }
    }
    
    @Test
    public void testDynamicCast_arrays_primitives_float() {
        final float[] primitiveArray = Variant.<float[]>dynamic_cast(float[].class, Variant.valueOf(new String[]{"1.1","2.2","3.3"}));
        Assert.assertNotNull(primitiveArray);
        Assert.assertEquals(3, primitiveArray.length);
        for(int i = 0; i < primitiveArray.length; ++i) {
            Assert.assertEquals(Float.parseFloat(String.format("%d.%d",i+1,i+1)), primitiveArray[i], 0.02);
        }
    }

    @Test
    public void testDynamicCast_arrays_primitives_double() {
        final double[] primitiveArray = Variant.<double[]>dynamic_cast(double[].class, Variant.valueOf(new String[]{"1.1","2.2","3.3"}));
        Assert.assertNotNull(primitiveArray);
        Assert.assertEquals(3, primitiveArray.length);
        for(int i = 0; i < primitiveArray.length; ++i) {
            Assert.assertEquals(Double.parseDouble(String.format("%d.%d",i+1,i+1)), primitiveArray[i], 0.02);
        }
    }
    
    @Test
    public void testDynamicCast_arrays_string() {
        final String[] objectArray = Variant.<String[]>dynamic_cast(String[].class, Variant.valueOf(new String[]{"1.1","2.2","3.3"}));
        Assert.assertNotNull(objectArray);
        Assert.assertEquals(3, objectArray.length);
        for(int i = 0; i < objectArray.length; ++i) {
            Assert.assertEquals(String.format("%d.%d",i+1,i+1), objectArray[i]);
        }
    }
    
    public static interface FooInterface {
        
    }
    
    @Test(expected=ClassCastException.class)
    public void testDynamicCast_arrays_interface() {
        Variant.<FooInterface[]>dynamic_cast(FooInterface[].class, Variant.valueOf(new String[]{"1.1","2.2","3.3"}));
    }
}
